using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;

namespace ProxyServer.Pool
{
    public struct BuffUnit
    {
        public BuffUnit(byte[] buffer, int offset, int count)
            : this()
        {
            this.Buffer = buffer;
            this.Offset = offset;
            this.Size = count;
        }

        public byte[] Buffer { get; private set; }

        public int Offset { get; private set; }

        public int Size { get; private set; }

        public int Count { get; set; }
    }

    public class BuffManager
    {
        private readonly int blockSize;
        private readonly int totalSize;
        private readonly int blockCount;
        private Byte[] buffer;
        private Stack<BuffUnit> freeIndexPool;
        private Semaphore semaphore;

        public BuffManager(int blockSize, int blockCount)
        {
            this.totalSize = blockSize * blockCount;
            this.blockSize = blockSize;
            this.blockCount = blockCount;

            Init();
        }

        protected void Init()
        {
            buffer = new byte[totalSize];

            freeIndexPool = new Stack<BuffUnit>(blockCount);

            for (var i = 0; i < blockCount; i++)
            {
                freeIndexPool.Push(new BuffUnit(buffer, i * blockSize, blockSize));
            }

            semaphore = new Semaphore(blockCount, blockCount);
        }

        public BuffUnit GetBuffer()
        {
            semaphore.WaitOne();

            return freeIndexPool.Pop();
        }

        public bool FreeBuffer(BuffUnit buffUnit)
        {
            freeIndexPool.Push(buffUnit);
            semaphore.Release();
            return true;
        }

        public void FreeBuffer(SocketAsyncEventArgs args)
        {
            freeIndexPool.Push((BuffUnit)args.UserToken);
            semaphore.Release();
            args.SetBuffer(null, 0, 0);
            args.UserToken = null;
        }

        public bool SetBuffer(SocketAsyncEventArgs args)
        {
            semaphore.WaitOne();

            var buffUnit = freeIndexPool.Pop();

            args.UserToken = buffUnit;

            args.SetBuffer(buffUnit.Buffer, buffUnit.Offset, buffUnit.Size);

            return true;
        }

        public void Clear()
        {
            lock (this)
            {
                freeIndexPool.Clear();
                buffer = null;
            }
        }
    }
}